#include "Catalogues.h"

Catalogues::Catalogues(std::string nombre) 
{
	this->nombre = nombre;
	this->abrirArchivos();
}
 
Catalogues::~Catalogues()
{
	this->cerrarArchivos();
}

void Catalogues::abrirArchivos()
{
	this->Terminos   =  new archivoTerminos(this->nombre + std::string("_terminos.dat"));
	this->IdTerminos =  new archivoIdTerminos(this->nombre + std::string("_idterminos.dat"));
	this->Documentos =  new archivoDocumentos(this->nombre + std::string("_documentos.dat"),this->nombre + std::string("_documentossec.dat") );
	this->TermDoc    =  new archivoTermDoc(this->nombre + std::string("_termdoc.dat"));
	this->DocTerm    =  new archivoDocTerm(this->nombre + std::string("_docterm.dat"));
	this->NGramas    =  new archivoNGramas(this->nombre + std::string("_ngramas.dat"));
}

void Catalogues::cerrarArchivos()
{
	delete Terminos;
	delete IdTerminos;
	delete Documentos;
	delete TermDoc;
	delete DocTerm;
	delete NGramas;
}

void Catalogues::borrarCatalogo()
{
	this->cerrarArchivos();
	remove((this->nombre + std::string("_terminos.dat")).c_str());
	remove((this->nombre + std::string("_terminos.dat.del")).c_str());
	remove((this->nombre + std::string("_terminos.dat.ind")).c_str());
	remove((this->nombre + std::string("_terminos.dat.seq")).c_str());
	remove((this->nombre + std::string("_idterminos.dat")).c_str());
	remove((this->nombre + std::string("_idterminos.dat.del")).c_str());
	remove((this->nombre + std::string("_idterminos.dat.ind")).c_str());
	remove((this->nombre + std::string("_idterminos.dat.seq")).c_str());
	remove((this->nombre + std::string("_documentos.dat")).c_str());
	remove((this->nombre + std::string("_documentos.dat.del")).c_str());
	remove((this->nombre + std::string("_documentos.dat.ind")).c_str());
	remove((this->nombre + std::string("_documentos.dat.seq")).c_str());
	remove((this->nombre + std::string("_documentossec.dat.del")).c_str());
	remove((this->nombre + std::string("_documentossec.dat.ind")).c_str());
	remove((this->nombre + std::string("_documentossec.dat.seq")).c_str());
	remove((this->nombre + std::string("_termdoc.dat.del")).c_str());
	remove((this->nombre + std::string("_termdoc.dat.ind")).c_str());
	remove((this->nombre + std::string("_termdoc.dat.seq")).c_str());
	remove((this->nombre + std::string("_docterm.dat.del")).c_str());
	remove((this->nombre + std::string("_docterm.dat.ind")).c_str());
	remove((this->nombre + std::string("_docterm.dat.seq")).c_str());
	remove((this->nombre + std::string("_ngramas.dat.del")).c_str());
	remove((this->nombre + std::string("_ngramas.dat.ind")).c_str());
	remove((this->nombre + std::string("_ngramas.dat.seq")).c_str());
	this->abrirArchivos();
}

std::vector<std::string> Catalogues::obtenerNGramas(const std::string termino,unsigned N)
{
	std::vector<std::string> ngramas;
	std::string ngrama, termino_aux;
	
	termino_aux = termino;
	
	termino_aux.insert(0,"$");
	termino_aux.push_back('$');
	unsigned largo = termino_aux.size();
	
	for (unsigned i=0;i<(largo-1);i++) {
		ngrama.clear();
		if (i+N<=largo)
			for (unsigned j=0;j<N;j++) 
			   ngrama.push_back(termino_aux[i+j]);
		ngramas.push_back(ngrama);
	}
	
	
	return ngramas;
}

int Catalogues::agregarDocumento(std::string ruta, std::vector<std::string> terminos)
{
	long idDoc;
	long idTerm;
	
	//alta del documento en el archivo de Documentos
	idDoc = this->Documentos->agregarDocumento(ruta);
	std::vector<std::string>::iterator i,j;
	std::vector<std::string> bigramas;
	
	for (i = terminos.begin(); i != terminos.end(); i++) {
                StaticUtils::trimo(*i);
                idTerm = this->Terminos->agregarPalabra(*i);
		this->IdTerminos->agregarTermino(idTerm,*i);
		this->TermDoc->agregarTermDoc(idTerm,idDoc);
		this->DocTerm->agregarRelacion(idDoc,idTerm);
		//!FALTA: NO REPETIR SI EL ID_TERM YA EXISTIA
		bigramas = obtenerNGramas(*i);
		for (j = bigramas.begin(); j != bigramas.end(); j++)
			this->NGramas->agregarNGrama(*j,idTerm);
	} 
	return 0;
}

int Catalogues::eliminarDocumento(std::string ruta)
{
	long idDoc;
	std::vector<long> terminos;
	std::vector<long>::iterator i;
	
	idDoc = this->Documentos->obtenerIdDocumento(ruta);
	if (idDoc == 0)
		return -1; // el documento no existe
		
	terminos = this->DocTerm->eliminarRelaciones(idDoc);
	
	for (i = terminos.begin(); i != terminos.end(); i++)
		this->TermDoc->eliminarTermDoc(*i,idDoc);
	
	
	return 0;
}

int Catalogues::modificarDocumento(std::string ruta, std::vector<std::string> terminos)
{
	if ((this->eliminarDocumento(ruta)) == -1)
		return -1; // el documento no existe
	return this->agregarDocumento(ruta,terminos);
}

void Catalogues::mostrarTerminosDoc()
{
	std::string rutadoc;
	long cantTerm = Terminos->obtenerCantidadPalabras();
	std::cout << "Cantidad de palabras: " << cantTerm << "\n";
	for (long id=1;id<=cantTerm;id++) {
		std::cout << id << ": " << IdTerminos->obtenerTermino(id) << " ";
		vector<long> documentos = TermDoc->obtenerDocumentos(id);
		vector<long>::iterator i;
		for (i = documentos.begin(); i != documentos.end(); i++) {
			rutadoc = this->Documentos->obtenerRutaDocumento(*i);
			std::cout << " (" << rutadoc << ")";
		}	
		std::cout << "\n";
	}	
	
}

void Catalogues::mostrarBigramas()
{
	vector<ClaveNGrama> lista_ngramas = this->NGramas->obtenerTodos();
	std::cout << "Bigramas: " << lista_ngramas.size() << "\n";
	vector<ClaveNGrama>::iterator j;
	for (j = lista_ngramas.begin(); j != lista_ngramas.end(); j++) {
			std::cout << *j << " " << "\n"; 
		}
}

double Catalogues::prodInterno(const std::vector<double> vect1, const std::vector<unsigned> vect2)
{
	if (vect1.size() != vect2.size())
		return -1; //error
	double pi=0;
	
	for (unsigned i=0;i<vect1.size();i++)
		pi += vect1[i]*vect2[i];
	return pi;	
}

std::vector<std::string> Catalogues::rankingQuery(std::vector<std::string> queryTerminos, std::vector<pair<unsigned,char> > relevancia)
{
	std::vector<long> IdPalabras; //vector con lista de id de terminos
	typedef std::vector<double> * pvector;
	std::map<long,pvector> mapaDocumentos; //mapa con documentos resultantes y sus respectivos vectores
	std::map<long,pvector>::iterator i_md;
	std::vector<std::string> resultados; //resultado de la consulta con las rutas de los docs	
	std::vector<std::string>::iterator i_qt;
	std::vector<long> ft; // vector con cantidad de documentos donde aparece cada termino
	std::vector<unsigned> vectorRelevancia; // vector con la relevancia de cada termino
	long ftd = 0; //frecuencia de un termino en cada documento
	double wtd = 0; //peso del termino en un documento
	long N = this->Documentos->getCantDocumentos();   //cantidad total de docs
	//id de docs ordenados por ranking
	std::priority_queue<double,vector<pair<double,long> > > idDocs;
	
	
	//obtenemos los id de los terminos consultados
	for (i_qt = queryTerminos.begin();i_qt != queryTerminos.end(); i_qt++)
		IdPalabras.push_back(this->Terminos->obtenerIdPalabra(*i_qt));	

	for (unsigned i=0;i<IdPalabras.size();i++) {
		vector<long> docs = this->TermDoc->obtenerDocumentos(IdPalabras[i]);
		ft.push_back(docs.size());
		//std::cout << "Palabra: " << IdPalabras[i] << " ft: " << ft[i] << "\n";
		for (unsigned j=0;j<docs.size();j++) {
			if (mapaDocumentos[docs[j]] == NULL)
				mapaDocumentos[docs[j]] = new std::vector<double>;
		}		
	}
	for (i_md = mapaDocumentos.begin();i_md != mapaDocumentos.end(); i_md++)
		for (unsigned i=0;i<IdPalabras.size();i++) {
			if (ft[i]==0) {
				wtd = 0;
			}
			else {	
				ftd = this->DocTerm->obtenerFrecuencia(i_md->first,IdPalabras[i]);
				wtd = ftd * log10( ((double)N) / ft[i] );
			}			
			((i_md)->second)->push_back(wtd);
		}
	
	/*
	// un vistazo!
	for (i_md = mapaDocumentos.begin();i_md != mapaDocumentos.end(); i_md++) {
		std::cout << "Documento: " << (i_md)->first << " valor: ";
		for (unsigned i=0;i<((i_md)->second)->size();i++) {
		 	std::cout << " (" << (*((i_md)->second))[i] << ") ";
		}
		std::cout << "\n";
    }*/
    
    for (unsigned i=0;i<relevancia.size();i++)
    	vectorRelevancia.push_back(relevancia[i].first);
    //calcula el producto interno e inserta ordenado los resultados
    for (i_md = mapaDocumentos.begin();i_md != mapaDocumentos.end(); i_md++) {
		double pi = this->prodInterno((*(i_md)->second), vectorRelevancia); 
		idDocs.push(pair<double,long>(pi,(i_md)->first));
		delete (i_md)->second; //destruye el vector
    } 
    //std::cout << "Ranking y idDocs:\n";
    while (!idDocs.empty()) {
     	//std::cout << (idDocs.top()).first << " id: " << (idDocs.top()).second << "\n";
     	resultados.push_back( this->Documentos->obtenerRutaDocumento((idDocs.top()).second) );
     	idDocs.pop();
    }
    
	return resultados;	
}

std::vector<long> Catalogues::calcularAND(const std::vector<long> vect1,const std::vector<long> vect2)
{
	std::vector<long> salida;
	std::vector<long> resultado( min(vect1.size(),vect2.size()) );
	std::vector<long>::iterator it,fin;
	std::vector<long> v1 = vect1;
	std::vector<long> v2 = vect2; 
	
	std::sort(v1.begin(),v1.end());
	std::sort(v2.begin(),v2.end());
	
	fin=std::set_intersection(v1.begin(),v1.end(),v2.begin(),v2.end(),resultado.begin());

	for (it=resultado.begin();it!=fin;it++)
		salida.push_back(*it);
	
	return salida;
} 

std::vector<std::string> Catalogues::validarComodines(const std::string expresion, const std::vector<std::string> terminos)
{
	std::vector<string> validos;
	std::string palabra = expresion;
	regex_t regex;
	
	palabra.insert(0,"^");
	palabra.push_back('$');
		unsigned pos = palabra.find("?"); // reemplazamos "?" por "."
	while (pos != std::string::npos) {
		palabra.replace(pos,1,".");
		pos = palabra.find("?",pos+1);
	}
	pos = palabra.find("*"); // reemplazamos "*" por ".*" 
	while (pos != std::string::npos) {
		palabra.insert(pos,".");
		pos = palabra.find("*",pos+2);
	}
	regcomp(&regex,palabra.c_str(),REG_EXTENDED|REG_ICASE);
	for (unsigned i=0;i<terminos.size();i++)
		if (regexec(&regex,terminos[i].c_str(),(size_t)0, NULL, 0) == 0)
			validos.push_back(terminos[i]);
		
	regfree(&regex);
	return validos;
}

std::vector<std::string> Catalogues::getTerminosComodin(std::string expresion)
{
	std::vector<std::string> resultados;
	std::vector<std::string> bigramas;
	std::vector<long> idRes; //id resultantes
	
	bigramas = obtenerNGramas(expresion);
	bool entro = false;
	for (unsigned i=0;i<bigramas.size();i++){
		if ( (bigramas[i].find('*') == bigramas[i].npos) && 
		     (bigramas[i].find('?') == bigramas[i].npos) ) {
			std::vector<long> idObt = this->NGramas->obtenerTerminos(bigramas[i]);
			if (!entro){
				idRes = idObt;}
			else {
				idRes = this->calcularAND(idRes,idObt);}
			entro = true;
		}
	}

	for (unsigned i=0;i<idRes.size();i++)
		resultados.push_back(this->IdTerminos->obtenerTermino(idRes[i]));
	
	resultados = validarComodines(expresion,resultados);
	
	return resultados;
}

std::vector<std::string> Catalogues::puntualQuery(std::vector<std::string> queryTerminos)
{
	std::vector<std::string>::iterator i_qt;
	std::vector<std::string> resultados;
	std::vector<long> IdPalabras; //vector con lista de id de terminos
	std::set<long> IdDocs; //set con id de terminos
	std::set<long>::iterator i_docs;
	
    //obtenemos los id de los terminos consultados
	for (i_qt = queryTerminos.begin();i_qt != queryTerminos.end(); i_qt++)
		IdPalabras.push_back(this->Terminos->obtenerIdPalabra(*i_qt));
	
	for (unsigned i=0;i<IdPalabras.size();i++) {
		std::vector<long> docs = this->TermDoc->obtenerDocumentos(IdPalabras[i]);   
		for (unsigned j=0;j<docs.size();j++)
			IdDocs.insert(docs[j]);	
	}
	
	for (i_docs = IdDocs.begin();i_docs != IdDocs.end(); i_docs++)
		resultados.push_back( this->Documentos->obtenerRutaDocumento(*i_docs) );
	
	
	return resultados;	
}
